Skip to content

refactor: redesign webview package as @microsoft/vscode-ext-webview#766

Merged
tnaum-ms merged 65 commits into
mainfrom
dev/tnaum/webview-api-refinements
Jul 6, 2026
Merged

refactor: redesign webview package as @microsoft/vscode-ext-webview#766
tnaum-ms merged 65 commits into
mainfrom
dev/tnaum/webview-api-refinements

Conversation

@tnaum-ms

Copy link
Copy Markdown
Collaborator

Summary

Redesigns the webview RPC package: replaces @microsoft/vscode-ext-react-webview with a new @microsoft/vscode-ext-webview (0.9.0-preview) built around four side-aware subpaths and composable primitives, migrates the extension onto it, and removes the old package.

What changed

  • New package @microsoft/vscode-ext-webview with four subpaths:
    • . (shared, no vscode/react), ./host, ./webview, ./react
  • New primitives: openWebview factory + options-bag WebviewController, attachTrpc (bring-your-own-panel), connectTrpc, initWebviewTrpc, and telemetry middleware bodies (telemetryMiddlewareBody / loggingMiddlewareBody) wired through a consumer-supplied TelemetryRunner adapter.
  • Hook split: useTrpcClient() returns the client directly; useRpcEvents() exposes the event channel.
  • Retired: createMiddleware, TelemetryContext, the old useTrpcClient tuple return, UseTrpcClientOptions.
  • Extension migration: the _integration layer and panel controllers moved onto the new package; construction-only panels now use an openAppWebview factory preset (the old WebviewControllerBase is gone).
  • Removed the deprecated @microsoft/vscode-ext-react-webview package and all references.
  • Docs: package README.md + ADVANCED.md, plus an internal migration manual.

Validation

Whole-repo lint clean, full Jest suite green (2571 tests / 146 suites), and npm run build green.

Draft for review.

tnaum-ms added 29 commits June 30, 2026 09:03
@tnaum-ms tnaum-ms marked this pull request as ready for review July 2, 2026 21:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 91 out of 99 changed files in this pull request and generated no new comments.

tnaum-ms added 5 commits July 5, 2026 20:16
…ot extension mode

The redesign chose the bundled-vs-dev source layout from extensionMode === Production, but the dev server (webpack serve) emits the bundled asset name (views.js). A webpack-bundled extension running in Development (the normal F5 dev flow, IS_BUNDLE=true, DEVSERVER=true) therefore requested the tsc dev file (index.js) from the dev server and got a 404, leaving the webview blank.

Restore the pre-redesign contract: a required isBundled option drives the layout; extensionMode stays CSP-only. The DocumentDB consumer passes isBundled: git commit -q -F /tmp/done-msg.txt && git push -q origin HEAD && echo pushed && git --no-pager log -1 --format='%h %s'ext.isBundle. Adds regression tests.
…ubfolder

Move rpcConcurrencyLogger (host) and reportObserverError (webview), plus their tests, into src/webviews/_integration/observability/ so the reference integration folder separates the router/transport wiring from the cross-cutting observability adapters. Update imports, the two external importers, the folder README, and add observability/README.md. No runtime behavior change.
…tion 7 (_integration observability regrouping)
…5 part 1)

The host-side dispatch logger (rpcConcurrencyLogger) emitted a console.log
on every completed RPC via the framework's consoleProcedureLogger. That line
is useful when debugging the extension but is dead weight (string formatting +
host-console I/O) on a shipped, installed build where no one watches that
console. Gate it behind extensionMode !== Production so dev/F5 and Test builds
still log while packaged builds never execute it. The concurrency telemetry
gauge stays unconditional and records in every mode, production included.

The mode is read per-call: ext.context is always populated by the time an RPC
fires (the webview is open, so the extension has activated) and the comparison
is O(1), so there is no module-load ordering risk and no measurable added cost.
The framework's consoleProcedureLogger stays the neutral, opt-in sink; the
gating decision lives in the DocumentDB adapter, preserving the package boundary.

Tests stub ext.context.extensionMode per case and add Test-mode (still logs) and
Production (no console line, gauge still recorded) coverage. Documented as
Iteration 9 in the PR-766 review; Iteration 10 (accumulating-telemetry
accumulate/flush redesign, part 2) staged as not-started.
@tnaum-ms

tnaum-ms commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator Author

Iteration 9 — console gate (R766-P05 part 1)

Following the Iteration 8 latency/load-time analysis, this commit implements part 1 of the R766-P05 follow-up.

What changedrpcConcurrencyLogger gates the per-op host-side console.log (the framework's consoleProcedureLogger.log) behind extensionMode !== Production:

if (ext.context.extensionMode !== vscode.ExtensionMode.Production) {
    consoleProcedureLogger.log(entry);
}

Why — the dispatch logger fires on every completed RPC. The console line is useful when debugging the extension (F5 / "Run Extension" → Development, and Test) but is dead weight (string formatting + host-console I/O) on a shipped, installed build where nobody watches that console. Gating it means dev builds keep zero-friction visibility and packaged builds never execute it. The concurrency telemetry gauge stays unconditional and records in every mode, production included.

Notes

  • The mode is read per call: ext.context is always populated by the time an RPC fires (the webview is open ⇒ the extension has activated), and the comparison is O(1) — no module-load ordering risk, no measurable added cost.
  • The framework's consoleProcedureLogger stays the neutral, opt-in sink; the gating decision lives entirely in the DocumentDB adapter, preserving the package/consumer boundary.
  • Tests stub ext.context.extensionMode per case and add a Test-mode case (still logs) and a Production case (no console line, gauge still recorded).

Scope — this closes the console half of R766-P05. The heavier half — callWithAccumulatingTelemetry running the full callWithTelemetryAndErrorHandling wrapper per op (which stays on in production by design) — is part 2, sequenced as Iteration 10 (Option 1: split the cheap accumulate path from the heavy flush path, and migrate all callers). Not started yet.

Validation: jest (4/4) · eslint (clean) · type-check (no errors) · prettier (unchanged). No user-facing strings.

Full decision record: docs/ai-and-plans/PRs/766-webview-ext-package-redesign/webview-ext-review.md (Iteration 9 change protocol + Iteration 10 plan). Commit: 8a279cf3.

… flush (R766-P05 part 2)

callWithAccumulatingTelemetry batched the telemetry emit but still ran the full
callWithTelemetryAndErrorHandling wrapper (IActionContext allocation, error-
handling wiring, performance.now, Object.entries copy loops, an await) on EVERY
call. On hot paths (per webview RPC, per keystroke) that per-call machinery is
the cost that adds up, and it runs in production where the gauge is meant to.

Redesign (Option 1 from the PR-766 review): the per-call path now runs the
populator synchronously against a plain TelemetrySample bag and folds the values
into in-memory batch totals - no IActionContext, no await, no
callWithTelemetryAndErrorHandling. The Azure pipeline is entered exactly once, on
flush, with the rolled-up event.

- Callback contract: (sample: TelemetrySample) => void (was (ctx: IActionContext)
  => T | PromiseLike<T> returning Promise<T | undefined>). Retires the
  TelemetryWithDistributions cast; distributions is now a first-class bag field.
  Every call site already void-ed the result and used a synchronous callback.
- Errors never accumulate: a populator throw discards the sample and is reported
  once through callWithTelemetryAndErrorHandling under the same callbackId (the
  only remaining heavy path, and rare).
- Added a finite guard in accumulate so a stray NaN/Infinity cannot poison a sum
  or min/max reduction.
- Unchanged: auto_duration_ms distribution, dist_* rollup keys, batch/interval
  throttle, flushAccumulatingTelemetry, flushed event name == callbackId.

Migrated all direct callers (ClustersExtension, DocumentDBShellPty x6,
collectionViewRouter, rpcConcurrencyLogger) and the meterSilentCatch shorthand;
meterSilentCatch callers needed no change. Tests rewritten for the sync sample-bag
API, adding finite-guard and 'per-call path never enters the telemetry pipeline,
only flush does' coverage. Documented as Iteration 10 in the PR-766 review.

Validation: jest 2668/2668, eslint (0 errors), tsc --noEmit clean, npm run build,
prettier. No user-facing strings.
@tnaum-ms

tnaum-ms commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator Author

Iteration 10 — accumulating-telemetry per-call cost (R766-P05 part 2)

Second half of the R766-P05 follow-up. Iteration 9 gated the console line; this one removes the heavier per-op cost that ran in production.

ProblemcallWithAccumulatingTelemetry batched the telemetry emit (20 calls / 30 s) but still ran the full callWithTelemetryAndErrorHandling wrapper — IActionContext allocation, error-handling wiring, performance.now(), three Object.entries copy loops, an await — on every call. On hot paths (per webview RPC, per keystroke) that per-call machinery is the cost that adds up.

Redesign (Option 1) — the per-call path now runs the populator synchronously against a plain TelemetrySample bag and folds values into in-memory batch totals: no IActionContext, no await, no callWithTelemetryAndErrorHandling. The Azure pipeline is entered exactly once, on flush, with the rolled-up event.

// before
callWithAccumulatingTelemetry('evt', (ctx) => {
  (ctx.telemetry as TelemetryWithDistributions).distributions.gauge = n;
  ctx.telemetry.measurements.hits = 1;
});

// after — plain bag, synchronous, void
callWithAccumulatingTelemetry('evt', (sample) => {
  sample.distributions.gauge = n; // first-class field, no cast
  sample.measurements.hits = 1;
});

API change — callback is now (sample: TelemetrySample) => void (was (ctx: IActionContext) => T | PromiseLike<T> returning Promise<T | undefined>). This also retires the TelemetryWithDistributions cast. Safe because every call site already void-ed the result and used a synchronous callback.

Preserved semantics

  • Errors never accumulate: a populator throw discards the sample and is reported once through callWithTelemetryAndErrorHandling under the same callbackId (the only remaining heavy path, and rare).
  • Auto-duration (auto_duration_ms), dist_*_min/max/sum/count rollups, batch/interval throttle, flushAccumulatingTelemetry, and "flushed event name == callbackId" all unchanged.
  • Added a finite guard in accumulate so a stray NaN/Infinity can't poison a sum or min/max reduction.

Callers migratedClustersExtension, DocumentDBShellPty (×6 trackers), collectionViewRouter, rpcConcurrencyLogger, and the meterSilentCatch shorthand. meterSilentCatch callers (schema store, connection strings, playground, tree items, …) needed no change — the shorthand's signature is unchanged.

Tests — helper test rewritten for the synchronous sample-bag API; added a finite-guard case and a "per-call path never enters the telemetry pipeline, only flush does" case (the redesign's core guarantee). rpcConcurrencyLogger test now invokes the captured callback with a sample bag directly.

Skill check — reviewed telemetry-instrumentation; it documents callWithTelemetryAndErrorHandling / publicProcedureWithTelemetry but not callWithAccumulatingTelemetry's callback shape, so no skill edit was needed.

Validation: jest 2668/2668 · eslint 0 errors (2 pre-existing unrelated require-await warnings in DocumentDBShellPty.ts) · tsc --noEmit clean · npm run build · prettier. No user-facing strings.

Full decision record: docs/ai-and-plans/PRs/766-webview-ext-package-redesign/webview-ext-review.md (Iteration 10). Commit: 0df2f8b0. This closes R766-P05 (both parts).

…teTelemetry (R766-P06)

After the Iteration 10 accumulate/flush split, the callWith... name was
misleading: the callWith... convention in @microsoft/vscode-azext-utils promises
'run my work inside a managed scope that hands me a full IActionContext
(telemetry + errorHandling + ui + valuesToMask) and auto-records duration/result/
errors for it.' The redesigned helper does none of that on the per-call path - it
fills a plain sample bag synchronously and returns void. Rename so the name stops
promising a scoped call. Behavior unchanged; rename + file move only.

- callWithAccumulatingTelemetry -> accumulateTelemetry (verb 'accumulate' signals
  'record a data point into a batch', not 'run my work in a scope').
- flushAccumulatingTelemetry -> flushAccumulatedTelemetry.
- AccumulatingTelemetryOptions -> AccumulateTelemetryOptions.
- Unchanged: TelemetrySample, AUTO_DURATION_DISTRIBUTION_KEY, meterSilentCatch.
- File: src/utils/callWithAccumulatingTelemetry.ts -> accumulatingTelemetry.ts
  (+ .test.ts) via git mv; all importers updated, including meterSilentCatch-only
  consumers and the two jest.mock paths.

The verb now encodes whether your callback runs: accumulateTelemetry does NOT run
your work; the deferred runWithAccumulatingTelemetry (issue #777) would, so a
runWith... name is honest there.

Reviewed the telemetry-instrumentation skill: it does not mention the accumulating
helper by any name, so no skill edit was needed.

Deliverable 2 (runWithAccumulatingTelemetry, wrap-an-action variant) filed as
enhancement issue #777 for future pickup; not built here.

Validation: jest 2668/2668, eslint 0 errors, tsc --noEmit clean, npm run build,
prettier. No user-facing strings. Documented as Iteration 11 in the PR-766 review.
@tnaum-ms

tnaum-ms commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator Author

Iteration 11 — accumulating-telemetry rename (R766-P06)

Naming follow-up to Iteration 10. After the accumulate/flush split, the callWith… name was actively misleading: the callWith… convention in @microsoft/vscode-azext-utils promises "run my work inside a managed scope that hands me a full IActionContext (telemetry + errorHandling + ui + valuesToMask) and auto-records duration/result/errors." The redesigned helper does none of that on the per-call path — it fills a plain sample bag, synchronously, and returns void. Behavior unchanged; rename + file move only.

Renames

  • callWithAccumulatingTelemetryaccumulateTelemetry
  • flushAccumulatingTelemetryflushAccumulatedTelemetry
  • AccumulatingTelemetryOptionsAccumulateTelemetryOptions
  • unchanged: TelemetrySample, AUTO_DURATION_DISTRIBUTION_KEY, meterSilentCatch
  • file src/utils/callWithAccumulatingTelemetry.tssrc/utils/accumulatingTelemetry.ts (+ .test.ts), via git mv (history preserved); all importers updated, including the meterSilentCatch-only consumers and both jest.mock('…') paths.

The teachable distinction — the verb now encodes whether your callback runs:

  • accumulateTelemetry(id, (sample) => …) — does not run your work (records a data point).
  • a future runWithAccumulatingTelemetry(id, (sample) => action) — genuinely runs your action, so a runWith… name is honest there.

Naming the parameter sample (not ctx) reinforces it at every call site: sample.measurements visibly lacks sample.ui / sample.errorHandling.

Telemetry skill — reviewed .github/skills/telemetry-instrumentation; it documents callWithTelemetryAndErrorHandling / publicProcedureWithTelemetry but does not mention the accumulating helper by any name, so no skill edit was needed.

Deliverable 2 deferred — the "wrap an action" variant (runWithAccumulatingTelemetry: runs the action, batches successes, emits failures immediately, rethrows) is not built here. It's specced and filed as #777 (enhancement, [telemetry] title) for pickup when richer hot-path telemetry is needed.

Validation: jest 2668/2668 · eslint 0 errors (4 pre-existing unrelated require-await warnings) · tsc --noEmit clean · npm run build · prettier. No user-facing strings.

Full record: docs/ai-and-plans/PRs/766-webview-ext-package-redesign/webview-ext-review.md (Iteration 11). Commit: 1ee371f2.

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

✅ Code Quality Checks

Check Status How to fix
Localization (l10n) ✅ Passed
ESLint ✅ Passed
Prettier formatting ✅ Passed

This comment is updated automatically on each push.

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

📦 Build Size Report

Metric Base (main) PR Delta
VSIX (vscode-documentdb-0.9.1.vsix) 8.01 MB 8.01 MB ⬆️ +1 KB (+0.0%)
Webview bundle (views.js) 5.88 MB 5.88 MB ⬆️ +2 KB (+0.0%)

Download artifact · updated automatically on each push.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 110 out of 118 changed files in this pull request and generated no new comments.

@tnaum-ms tnaum-ms enabled auto-merge July 6, 2026 09:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

3 participants